Урок 9. Шейдеры в Direct3D10
В этом уроке вы узнаете: какая архитектура шейдеров; какую роль выполняют шейдеры в DirectX приложении; что такое язык HLSLи для чего он предназначен, ознакомитесь со базовыми принципами программирования HLSL.
Шейдеры
Из предыдущих уроков вы уже знакомы с шейдерами и применяли их практически. В данном уроке мы не будем использовать их как дополнение к приложению, а взглянем на сами шейдеры и на их код.
Сейчас шейдеры являются ключевым аспектом DirectXприложения. Фактически, трудно что-нибудь сделать без их использования. Вся графика игр, многочисленные спецэффекты, и тот реализм, который на сегодняшний день достигла графическая часть игр – это всё получилось благодаря использованию шейдеров. Взглянем лишь на некоторые примеры использования шейдеров:
- Обычное применение: матрицы камеры и освещениями в вершинномшейдере, текстуры и определение текущего цвета пикселя в пиксельном шейдере
- Композиции из нескольких текстур, использования глубоких текстур, для тонкой детализации ландшафта и моделей
- Эффекты освещения и теней
- Системы частиц – туман, задымление а также различные виды вспышек
На самом деле, данный список можно продолжать, кажется бесконечно. Существует слишком много примеров применения шейдеров, чтобы даже их просто перечислить. Запустите любую новую игру и существующий список применения шейдеров может быть пополнен несколькими, а то и десятками новых применений шейдеров.
Особенности компиляции и загрузки шейдера
Хотя шейдер является очень низкоуровневой структурой – он должен выполняться очень быстро, но вы можете его писать на языке, подобном C++, этот язык называется HLSL. Это потому, что шейдер на ходу компилируется в машинный код видеоускорителя. Кроме того, не нужен специальный компилятор. Язык HLSLявляется стандартным и его компиляция осуществляется на ходу любой 3dсистемой, например DirectXили OpenGL. Таким образом, вы просто указываете на исходный файл формата .fxи он автоматически загружается, компилируется и выполняется непосредственно в вашем 3dприложении.
Язык HLSL
Взглянем на пример шейдера с точки зрения программирования. Посмотрим на типичный код шейдера:
//------------------------------------------------------------------------------------ // Constant Buffer Variables //------------------------------------------------------------------------------------ Texture2D g_txDiffuse; SamplerState samLinear { Filter = MIN_MAG_MIP_LINEAR; AddressU = Wrap; AddressV = Wrap; }; struct VS_INPUT { float3 Pos : POSITION; //position float3 Norm : NORMAL; //normal float2 Tex : TEXCOORD0; //texture coordinate }; struct PS_INPUT { float4 Pos : SV_POSITION; float3 Norm : TEXCOORD0; float2 Tex : TEXCOORD1; }; //------------------------------------------------------------------------------------ // Vertex Shader //------------------------------------------------------------------------------------ PS_INPUT VS( VS_INPUT input ) { PS_INPUT output = (PS_INPUT)0; output.Pos = mul( float4(input.Pos,1), World ); output.Pos = mul( output.Pos, View ); output.Pos = mul( output.Pos, Projection ); output.Norm = mul( input.Norm, World ); output.Tex = input.Tex; return output; } //------------------------------------------------------------------------------------ // Pixel Shader //------------------------------------------------------------------------------------ float4 PS( PS_INPUT input) : SV_Target { //calculate lighting assuming light color is <1,1,1,1> float fLighting = saturate( dot( input.Norm, vLightDir ) ); float4 outputColor = g_txDiffuse.Sample( samLinear, input.Tex ) * fLighting; outputColor.a = 1; return outputColor; } //------------------------------------------------------------------------------------ // Technique //------------------------------------------------------------------------------------ technique10 Render { pass P0 { SetVertexShader( CompileShader( vs_4_0, VS() ) ); SetGeometryShader( NULL ); SetPixelShader( CompileShader( ps_4_0, PS() ) ); } } |
Вы видите, что весь код шейдера может быть разбит на четыре ключевых раздела. Это: объявления глобальных переменных – констант шейдера, код вершинного буфера, код пиксельного буфера, и наконец, описание техники. В дальнейших разделах мы поэтапно рассмотрим структуру языка HLSL.
Типы данных HLSL
В каждом языке существуют различные встроенные типы данных. В языке HLSLтипы данных носят некоторый математический оттенок, то есть в нем много типов данных наподобие векторов и матриц. Итак, таблица типов данных HLSL:
Тип данных | Примечание |
Buffer | Массив |
Scalar | Одно компонентная скалярная величина |
Vector,Matrix | Несколько компонентный вектор или матрица |
Sampler, Shader, 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 используется для описания типа фильтрации текстуры. Тип Shaderиспользуется для техники. Тип Textureдля указания на используемую текстуру. Примеры определений для этих типов данных. Тип данных Sampler:
sampler MeshTextureSampler = sampler_state { Texture = <g_MeshTexture>; MipFilter = LINEAR; MinFilter = LINEAR; MagFilter = LINEAR; }; |
Тип данных Shader:
technique10 Render { pass P0 { SetVertexShader( CompileShader( vs_4_0, VS() ) ); SetGeometryShader( NULL ); SetPixelShader( CompileShader( ps_4_0, PS() ) ); } } |
Тип данных Texture:
texture g_MeshTexture; Output.RGBColor = tex2D(MeshTextureSampler, In.TextureUV) * In.Diffuse; |
Тип данных Texture, еще один вариант:
Texture2D g_MeshTexture; Output.RGBColor = g_MeshTexture.Sample(MeshTextureSampler, In.TextureUV) * In.Diffuse; |
Операции ветвления кода в 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, определяющую цвет точки.
float 4 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не так сложен как многие другие языки, так как программы на нем – точнее фрагменты кода для пиксельного и вершинного шейдера – получаются очень короткими.
Заполнен: DirectX10
