Компиляция шейдеров в HLSL
Как мы узнали ранее, шейдеры имеют определяющее значение в создании графики в Direct3D 11. В данном уроке я покажу наиболее простой способ загрузки шейдеров в программу. Прежде всего нужно создать файлы, где будет храниться код шейдеров.
Создание файлов с шейдерами
Шейдеры хранятся в отдельных файлах, имеющих расширение .hsls. Для данного урока я добавил в проект два файла: PixelShader.hlsl и VertexShader.hlsl. Файл с шейдером можно добавить через пункт меню Project → Add New Item и, в открывшемся окне, выбрав нужный тип шейдера:

Компиляция шейдеров
В DirectX 11 под Windows 8 шейдер можно скомпилировать тремя способами. Два из них используют устройство D3D11. Третий способ - функция D3DCompileFromFile, относящаяся к HLSL. Вот эту функцию мы и будем использовать для компиляции шейдеров. Просто это самый быстрый и простой способ.
Замечу, что в документации сказано, что приложения, в которых используется D3DCompileFromFile, нельзя размещать в Windows Store.
Для использования шейдерного компилятора нужно подключить заголовочный файл D3DCompiler.h и библиотеку d3dcompiler.lib:
!1?#include <D3Dcompiler.h> #pragma comment(lib,"D3dcompiler.lib") ?1!Функция D3DCompileFromFile
Функция D3DCompileFromFile компилирует HLSL шейдеры. Она принимает файл с исходным кодом шейдера и возвращает (предпоследний аргумент) экземпляр интерфейса ID3DBlob (blob - сокращение от Binary Large Object - большой двоичный объект, т.е. это просто большой массив данных):
!1?HRESULT WINAPI D3DCompileFromFile( LPCWSTR pFileName, const D3D_SHADER_MACRO *pDefines, ID3DInclude *pInclude, LPCSTR pEntrypoint, LPCSTR pTarget, UINT Flags1, UINT Flags2, ID3DBlob **ppCode, ID3DBlob **ppErrorMsgs );?1!1. pFileName - имя файла с исходным кодом шейдера.
2. pDefines - массив макросов шейдера. Пока просто передаём NULL.
3. pInclude - данный аргумент задаётся, когда в файле шейдера присутствует директива #include. Ставим NULL.
4. pEntrypoint - точка входа шейдера, т.е. имя функции с кодом шейдера. По умолчанию Visual Studio использует main.
5. pTarget - версия и тип шейдера: compute, domain, geometry, hull, pixel, vertex. Нам пока нужны последние два: пиксельный и вершинный. В DirectX 11 нужно использовать 5-ую версию шейдеров. Передаём значения: vs_5_0 - вершинный, ps_5_0 - пиксельный.
6-7. Флаги. Пока оставляем NULL.
8. Экземпляр ID3DBlob, куда будет сохранён скомпилированный шейдер.
9. ppErrorMsgs - здесь будут сохранены сообщения об ошибках. Передаём NULL.
Итак, давайте скомпилируем вершинный и пиксельный шейдеры:
!1?ID3DBlob* vsBlob; ID3DBlob* psBlob; D3DCompileFromFile(L"VertexShader.hlsl", NULL, NULL, "main", "vs_5_0", NULL, NULL, &vsBlob, NULL); D3DCompileFromFile(L"PixelShader.hlsl", NULL, NULL, "main", "ps_5_0", NULL, NULL, &psBlob, NULL);?1!Вершинный шейдер мы компилируем в vsBlob, а пиксельный - в psBlob.
Создание шейдерных объектов в DirectX 11
Имея скомплировнный шейдер, с помощью устройства D3D11 можно создать шейдерные объекты - это представление шейдеров в программе. Для создания шейдеров разных типов интерфейс ID3D11Device использует разные методы. Нам нужно два метода: ID3D11Device::CreateVertexShader и ID3D11Device::CreatePixelShader. Они очень похожи (отличается только последний аргумент). Рассмотрим прототип первого:
!1?HRESULT CreateVertexShader( const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11VertexShader **ppVertexShader );?1!1. pShaderBytecode - адрес скомпилированного шейдера в памяти.
2. BytecodeLength - размер скомпилированного шейдера.
У интерфейса ID3DBlob есть два метода, которые как раз хранят два предыдущих значения: GetBufferPointer - адрес скомпилированного шейдера, GetBufferSize - размер. Эти методы не принимают аргументов.
3. pClassLinkage - аргумент используется для динамического связывания. Ставим NULL.
4. ppVertexShader - в этой переменной будет сохранён созданный шейдерный объект.
Посмотрим на код:
!1?ID3D11VertexShader* vs; ID3D11PixelShader* ps; dev->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), NULL, &vs); dev->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), NULL, &ps);?1!Осталось сделать созданные шейдеры активными:
!1?devContext->VSSetShader(vs, NULL, NULL); devContext->PSSetShader(ps, NULL, NULL);?1!Здесь контекст устройства устанавливает текущие вершинный и пиксельный шейдеры. Второй и третий аргументы на данный момент не важны.
Заключение
В прикреплённом проекте в шейдерах используется код, созданный Visual Studio по умолчанию. Данные пиксельный и вершинный шейдеры ничего не делают с данными. Когда вы запустите программу, то не увидите никаких различий. Убедитесь (в отладчике), что vsBlob, psBlob, vs и ps имеют реальные адреса (не равны нулю).
В этом уроке мы научились настраивать две важнейших стадии графического конвейера DirectX 11: вершинный и пиксельный шейдеры. Тепепь нам осталось снабдить графических конвейер данными.
Комментарии