Урок 9. Шейдеры в Direct3D11
Урок 9. Шейдеры в Direct3D11
В этом уроке вы узнаете: какая архитектура шейдеров; какую роль выполняют шейдеры в DirectX приложении; что такое язык HLSL и для чего он предназначен, ознакомитесь со базовыми принципами программирования HLSL.
Шейдеры
Из предыдущих уроков вы уже знакомы с шейдерами и применяли их практически. В данном уроке мы не будем использовать их как дополнение к приложению, а взглянем на сами шейдеры и на их код.
Сейчас шейдеры являются ключевым аспектом DirectX приложения. Фактически, трудно что-нибудь сделать без их использования. Вся графика игр, многочисленные спецэффекты, и тот реализм, который на сегодняшний день достигла графическая часть игр – это всё получилось благодаря использованию шейдеров. Взглянем лишь на некоторые примеры использования шейдеров:
- Обычное применение: матрицы камеры и освещениями в вершинномшейдере, текстуры и определение текущего цвета пикселя в пиксельном шейдере
- Композиции из нескольких текстур, использования глубоких текстур, для тонкой детализации ландшафта и моделей
- Эффекты освещения и теней
- Системы частиц – туман, задымление а также различные виды вспышек
На самом деле, данный список можно продолжать, кажется бесконечно. Существует слишком много примеров применения шейдеров, чтобы даже их просто перечислить. Запустите любую новую игру и существующий список применения шейдеров может быть пополнен несколькими, а то и десятками новых применений шейдеров.
Особенности компиляции и загрузки шейдера
Хотя шейдер является очень низкоуровневой структурой – он должен выполняться очень быстро, но вы можете его писать на языке, подобном C++, этот язык называется HLSL. Это потому, что шейдер на ходу компилируется в машинный код видеоускорителя. Кроме того, не нужен специальный компилятор. Язык HLSL является стандартным и его компиляция осуществляется на ходу любой 3d системой, например DirectX или OpenGL. Таким образом, вы просто указываете на исходный файл формата .fx и он автоматически загружается, компилируется и выполняется непосредственно в вашем 3d приложении.
Язык HLSL
Взглянем на пример шейдера с точки зрения программирования. Посмотрим на типичный код шейдера:
//------------------------------------------------------------------------------------ // Constant Buffer Variables //------------------------------------------------------------------------------------ Texture2D txDiffuse : register( t0 ); SamplerState samLinear : register( s0 ); //------------------------------------------------------------------------------------ cbuffer ConstantBuffer: register( b0 ) { matrix World; matrix View; matrix Projection; float4 vMeshColor; }; //------------------------------------------------------------------------------------ struct VS_INPUT { float4 Pos : POSITION; float2 Tex : TEXCOORD0; }; struct PS_INPUT { float4 Pos : SV_POSITION; float2 Tex : TEXCOORD0; }; //------------------------------------------------------------------------------------ // Vertex Shader //------------------------------------------------------------------------------------ PS_INPUT VS( VS_INPUT input ) { PS_INPUT output = (PS_INPUT)0; output.Pos = mul( input.Pos, World ); output.Pos = mul( output.Pos, View ); output.Pos = mul( output.Pos, Projection ); output.Tex = input.Tex; return output; } //------------------------------------------------------------------------------------ // Pixel Shader //------------------------------------------------------------------------------------ float4 PS( PS_INPUT input) : SV_Target { return txDiffuse.Sample( samLinear, input.Tex ) * vMeshColor; } |
Вы видите, что весь код шейдера может быть разбит на три ключевых раздела. Это: объявления глобальных переменных – констант шейдера представленных в виде константного буфера, код вершинного шейдера, и наконец, код пиксельного шейдера. В дальнейших разделах мы поэтапно рассмотрим структуру языка HLSL.
Типы данных HLSL
В каждом языке существуют различные встроенные типы данных. В языке HLSL типы данных носят некоторый математический оттенок, то есть в нем много типов данных наподобие векторов и матриц. Итак, таблица типов данных HLSL:
Тип данных | Примечание |
Buffer | Массив |
Scalar | Одно компонентная скалярная величина |
Vector,Matrix | Несколько компонентный вектор или матрица |
Sampler, Texture | Встроенные типы данных – сэмплер, текстура |
Struct, User Defined | Пользовательский тип данных |
Рассмотрим эти типы данных. Тип данных Scalar определяет скалярную величину и может быть одним из следующих:
bool int uint half float double |
Тип данных Vector и Matrix обозначается добавлением количества компонент к типу данных scalar. Например, Float4 определяет четырехкомпонентный вектор. Для матрицы, например Float4x4 добавляется NxN, то есть указывается размеренность матрицы. Пример определения вектора и матрицы:
float3 fVector = { 0.2f, 0.3f, 0.4f }; double3x3 dMatrix; float2x2 fMatrix = { 0.0f, 0.1, // row 1 2.1f, 2.2f // row 2 }; |
Типы Sampler используется для описания типа фильтрации текстуры. Тип Texture для указания на используемую текстуру. Примеры определений для этих типов данных, тип данных Sampler и Texture:
Texture2D txDiffuse : register( t0 ); SamplerState samLinear : register( s0 ); |
ТипданныхTexture:
float4 Color=txDiffuse.Sample( samLinear, input.Tex ) * vMeshColor; |
Операции ветвления кода в HLSL
В большинстве случаев код HLSL выполняется построчно, то есть одна строка, или инструкция потом следующая и так далее. Но в некоторых случаях, можно добавлять циклы и условные переходы. Обратите внимание, что так как HLSL должен работать быстро, то ветвления в коде HLSL должны быть скорее исключением, чем практикой.
Операция ветвления | Примечание |
break | Операция прерывания цикла |
continue | Продолжение цикла |
discard | Специфическое ветвление для пиксельногошейдера |
do | Ветвление |
for | Цикл |
if | Условный переход |
switch | Операция выбора |
while | Ветвление |
Операции ветвления в HLSL имеют тот же синтаксис, что и в языке C++.
Определения функций и блоки в HLSL
Остальная структура языка может быть сведена к следующей таблице:
Элемент | Примечание |
Функции | Определение функций |
Операции ветвления | См. предыдущий раздел |
Блоки | Блок определяется символами {} |
Выражение return | Операция возвращения из блока функции |
Выражения | Любое математическое выражение а также оператор присваивания. |
Операторы | Пример символов операторов: +,-,*,/,=,+=,++,<<,>>,==,!=,>=,! |
Встроенные функции | Intrinsic Functions |
Определение функций имеет некоторые характерные черты в HLSL. Об этом далее.
Типы данных, для вершинного и пиксельного шейдера
Для вершинного и пиксельного шейдера существуют определенные типы данных, присущие им в качестве входящих данных и выходящих функций. Итак, вершинныйшейдер принимает в себя входящие данные, которые должны полностью соответствовать формату вершинного буфера. Пример формата входящих данных для вершинного шейдера:
struct VS_INPUT { float3 Pos : POSITION; //position float3 Norm : NORMAL; //normal float2 Tex : TEXCOORD0; //texture coordinate }; |
Выходящими данные вершинныйшейдер возвращает операцией return. Они должны содержать структуру, хранящую координаты а также любые другие произвольные данные.
Выходящие данные вершинногошейдера являются входящими данными пиксельного шейдера:
struct PS_INPUT { float4 Pos : SV_POSITION; float3 Norm : TEXCOORD0; float2 Tex : TEXCOORD1; }; |
Пример входящего формата пиксельного шейдера эта структура PS_INPUT. Пиксельный шейдер операцией return может возвратить единственную переменную формата float4, определяющую цвет точки.
float4 color= g_txDiffuse.Sample( ... return color; |
Встроенные функции HLSL
Встроенные функции HLSL позволяют осуществлять многие операции, которые невозможно выполнить обычным кодом HLSL. Встроенные функции обозначаются IntrinsicFuncions. В число этих функций входят математические функции, операции с матрицами и векторами, операции выборки из текстур а также много другое. Вот пример некоторых из встроенных функций HLSL:
abs sin cos dot cross fmod log mul max min round sqrt tan tex1d tex2d texCUBE |
В этом отрывке кода из вершинного шейдера
output.Pos = mul( output.Pos, View ); output.Pos = mul( output.Pos, Projection ); output.Norm = mul( input.Norm, World ); |
легко найти места, в которых вызывается встроенная функция шейдера mul, соответствующая операции умножения матриц.
Заключение
В этом уроке вы познакомились непосредственно с тем, что представляет из себяшейдер. HLSL не так сложен как многие другие языки, так как программы на нем – точнее фрагменты кода для пиксельного и вершинного шейдера – получаются очень короткими.
Заполнен: DirectX11
