Создание игр » DirectX 11, DirectX 9, Featured » Shader (Шейдер)
Shader (Шейдер)
Shader – это специальная программа для графического процессора (GPU, Graphical Processing Unit), управляющая поведением определённой (шейдерной) стадии графического конвейера видео-карты, занимающаяся обработкой входных данных и “отдающая” результат обработки этих данных. В данный момент наиболее часто используются вершинные и пиксельные шейдеры (vertex & pixel shaders), но на самом деле в Direct3D существует не два, а больше видов шейдеров, которые отличаются как назначением, так и способами их применения:
- Вершинный шейдер (vertex shader) – шейдер, занимающиеся обработкой вершин модели
- Пиксельный шейдер (pixel shader) – шейдер, выполняющийся для каждого выводимого на экран пиксела
- Геометрический шейдер (geometry shader) (DirectX 10) – это шейдер, обрабатывающий геометрию. В отличие от вертексного шейдера, он обрабатывает не отдельные вершины, а наборы вершин, представляющих из себя геометрические примитивы (например, треугольники)
- Hull-Shader – (DirectX 11) шейдер тесселяции
- Domain-Shader – (DirectX 11) шейдер калькуляции внутри патча
Шейдеры для Direct3D пишутся на специальном языке шейдеров, который называется HLSL (High Level Shader Language) и имеет формат, весьма схожий с форматом языка C++, но со значительными упрощениями и меньшим функционалом.
Загрузка шейдера в Direct3D обычно происходит в два этапа:
- Компиляция кода из исходника HLSL с помощью функции D3DXCompileShader
- Создание самого шейдера, с помощью функций CreatePixelShader/CreateVertexShader (Direct3D 9)
Вот достаточно простой пример загрузки и компиляции щейдеров:
// это функция получения файла в виде строки string GetFileAsString(const string& strFileName) { string content; std::ifstream in(strFileName.c_str(), std::ios::binary); std::istreambuf_iterator<char> begin(in), end; while(begin != end) content += *begin++; return content; } // объявляем переменные LPD3DXBUFFER pErrors = NULL; LPD3DXBUFFER pShaderBuff = NULL; LPD3DXCONSTANTTABLE pConstTableVS = NULL; LPD3DXCONSTANTTABLE pConstTablePS = NULL; LPDIRECT3DPIXELSHADER9 m_pPixelShader = NULL; LPDIRECT3DVERTEXSHADER9 m_pVertexShader = NULL; // вертексный шейдер std::string srcVS = GetFileAsString("vs.hlsl"); D3DXCompileShader(srcVS.c_str(), srcVS.length(), NULL, NULL, "vertex_shader", "vs_2_0", D3DXSHADER_OPTIMIZATION_LEVEL3, &pShaderBuff, &pErrors, &pConstTableVS); m_pd3dDevice->CreateVertexShader(( DWORD* )pShaderBuff->GetBufferPointer(), &m_pVertexShader); pShaderBuff->Release(); // пиксельный шейдер std::string srcPS = GetFileAsString("ps.hlsl"); D3DXCompileShader(srcPS.c_str(), srcPS.length(), NULL, NULL, "pixel_shader", "ps_2_0", D3DXSHADER_OPTIMIZATION_LEVEL3, &pShaderBuff, &pErrors, &pConstTablePS); m_pd3dDevice->CreatePixelShader(( DWORD* )pShaderBuff->GetBufferPointer(), &m_pPixelShader); pShaderBuff->Release(); |
А использование шейдеровов ещё проще:
// устанавливаем шейдеры m_pd3dDevice->SetPixelShader(m_pPixelShader); // pixel shader m_pd3dDevice->SetVertexShader(m_pVertexShader); // vertex shader // рисуем треугольник m_pd3dDevice->DrawPrimitiveUP(D3DPT_TRIANGLELIST, 1/* 1 треугольник */, v /* данные брать отсюда*/, sizeof(VertPosDiffuse)/*размер вертекса*/); |
Код самих шейдеров. Vertex shader:
struct VS_OUTPUT { float4 Pos : POSITION; float4 Color: COLOR0; }; void vertex_shader(float3 Pos : POSITION, float4 Color: COLOR0, out VS_OUTPUT Out) { Out.Pos = float4(Pos, 1); Out.Color = Color; } |
Pixel shader:
float4 pixel_shader(float4 Color: COLOR0) : COLOR { return Color; } |
Проект с исходным кодом (включая шейдеры) к статье: Скачать.
В данном случае шейдеры, фактически, выполняют самый минимум работы – просто передают исходные данные на отрисовку, даже никак их не обрабатывая. Однако, в следующих уроках (Урок
Дополнительную информацию о шейдерах так же можно почерпнуть на следующих ресурсах:
Раздел: DirectX 11, DirectX 9, Featured · Теги: Direct3D, DirectX, Pixel shader, Shader, Vertex shader
А как работать с шейдерами с несколькими проходами, используя чистый LPDIRECT3DPIXELSHADER9? без d3dx.
Sergey, спасибо за вопрос! Это хороший повод сделать отдельную статью по теме mulipass shaders.
Пока же опишу вкратце. Вообще, многопроходные шейдеры, это “наворот” системы эффектов DirectX. Если же их не использовать, каждый проход шейдера будет выглядеть просто как отдельный шейдер и рендеринг геометрии с этим шейдером.
Т.е. вам надо будет загрузить два шейдера:
И рендерить геометрию (как и в случае ID3DXEffect) в два прохода. Код рендера станет вот таким:
При этом, естественно, будут действовать те же самые правила, что и при работе с пассами ID3DXEffect, т.е. вам надо не забыть активировать блендинг, либо альфа-тест, либо делать clip пикселей во втором шейдере – иначе результат работы второго шейдера полностью затрёт собой результат работы первого шейдера.
Кроме того, надо не забыть настроить (сразу после рендера первого шейдера и перед вторым шейдером) рендер-стейт для Z-буфера. Как и большинство, я обычно рендерю с:
для второго же шейдера будет необходимо использовать вместо D3DCMP_LESS либо D3DCMP_LESSEQUAL, либо D3DCMP_EQUAL, иначе второй шейдер ничего не нарисует (т.к. сработает отсечение по Z-буферу).
Огромное спасибо, ваш блог в закладках
только вот мое мнение я думаю сейчас со статьями лучше делать упор на 10 или 11 директ икс, по 9 инфы на русском много, а вот по 10 и 11 нефига толком нет.
Sergey, не за что! Рад был помочь
Уроки пока только по DirectX 9, т.к. он пока ещё достаточно распространён и на нём очень просто делать несложные игры, что я и планирую продемонстрировать в первых уроках.
Что же касается DirectX 10/11 – будут уроки и по DirectX 11 (10 не планируется), но немного позже, ближе к лету.
Компилировать шейдер можно сразу из файла с использованием функции D3DXCompileShaderFromFile.
Можно. Но часто это не удобно. Например, в данный момент мы можем модифицировать функцию GetFileAsString что бы она могла читать “файлы” не только из файловой системы, но и из пак-файлов или по сети и при этом переделывать логику самой программы не придётся – код останется неизменным.
По идее, так лучше работать со всеми данными: конфигами, текстурами, моделями, шейдерами и т.д. Тогда простым дополнением/изменением одной единственной функции (или 2-3, которые, скажем, позволяют получать текстовые/бинарные данные) мы сможем менять логику поведения всей программы.
Я ещё вернусь к рассмотрению этого вопроса, когда руки дойдут до статей о пак-файлах.
У меня при запуске release.exe вылетает с ошибкой.
1>FXC : error X4541: vertex shader must minimally write all four components of POSITION
что же делать
Пока всё ок. Но чую скоро начну мучить вопросами…:)
Ребят прошу помощи. При компиляции пишет вот что:
Ошибка 4 error LNK2019: unresolved external symbol _D3DXCompileShader@40 referenced in function “int __cdecl InitD3D(struct HWND__ *,int,int)” (?InitD3D@@YAHPAUHWND__@@HH@Z) axmd_engine.obj axmd_engine
не понимаю в чем проблема. Вроде все указано везде правильно.