HxSL - язык шейдеров Haxe
Современная трехмерная графика с аппаратным ускорением использует шейдеры для выполнения нескольких основных задач:
- вершинные шейдеры (vertex shaders)
используются для трансформирования и проецирования каждой точки геометрии в 2D пространство, а также установки "переменных" для попиксельной интерполяции и последующего использования в пиксельном шейдере - пиксельные шейдеры
(далее по тексту пиксельные шейдеры - pixel shaders, в оригинале и OpenGL используется термин - fragment shaders прим.пер.)
используются для смешивания разных текстур и цветов в цвет одного экранного пикселя
Благодаря поддержке макросов, стало возможным разработать высокоуровневый язык шейдеров HxSL, который использует синтаксис Haxe и может непосредственно встраиваться в исходный код на Haxe
Синтаксис шейдеров
Синтаксис шейдеров соответствует синтаксису Haxe, однако поддерживается лишь его подмножество. Вот пример шейдера на HxSL :
var input : { pos : Float3, uv : Float2 }; var tuv : Float2; function vertex( mpos : Matrix, mproj : Matrix ) { out = pos.xyzw * mpos * mproj; tuv = uv; } function fragment( t : Texture ) { out = t.get(tuv,wrap); }
Выражение (expression) шейдера состоит из следующих частей
- объект
input, который объявляет хранящиеся в буфере вершин данные - ноль или множество
переменных, которые будут записаны вершинным шейдером, а затем интерполированы GPU и считаны пиксельным шейдером - одна функция вершинного шейдера, с параметрами (постоянными для всех вершин)
- одна функция пиксельного шейдера, с параметрами (постоянными для всех пикселей)
Определены следующие переменные:
Float: одиночное скалярное значениеFloat2,Float3,Float4: набор из 2,3,4 floatMatrixorM44: матрица float 4x4M33,M43andM34: матрица соответствующей формыTexture: 2D текстурыCubeTexture: кубическая текстура (6 фейсов)
Каждый шейдер пишет в переменную out типа Float4
Локальные переменные
В шейдере HxSL можно объявлять локальные переменные, при желании - с инициализирующим значением:
function vertex(...) { var tpos = pos.xyzw * mpos; out = tpos * mproj; }
Либо их можно объявить позже, тогда должен быть указан тип:
function vertex(...) { var tpos : Float4; tpos = pos.xyzw * mpos; out = tpos * mproj; }
Константы
Константы могут быть одиночным скалярным значением или группой скаляров-констант, объявленной как массив Haxe:
0.5 // Float [0.5,1.5] // Float2 [1,2,3] // Float3 [1,2,3,4.5] // Float4
Операции
Список операций, доступных в HxSL приведен ниже. Обозначение FloatX может быть Float, Float2, Float3 или Float4 (только одного типа из перечисленных).
Все операции можно использовать двумя способами:
// стандартный C-like : add(x,y) // объектно-ориентированный : x.add(y)
add(or+) : сложениеfunction add( a : FloatX, b : FloatX ) : FloatX;
sub(or-) : вычитаниеfunction sub( a : FloatX, b : FloatX ) : FloatX;
mul(or*) : умножение, либо проекция вектора, либо умножение матриц :function mul( a : FloatX, b : FloatX ) : FloatX; function mul( a : Float4, m : Matrix ) : Float4; function mul( a : Matrix, b : Matrix ) : Matrix;
div(or/) : делениеfunction div( a : FloatX, b : FloatX ) : FloatX;
pow: степень a^bfunction pow( a : FloatX, b : FloatX ) : FloatX;
min: минимум из двух значенийfunction min( a : FloatX, b : FloatX ) : FloatX;
max: максимум из двух значенийfunction max( a : FloatX, b : FloatX ) : FloatX;
dot(илиdp,dp3,dp4) : скалярное произведение (dot product) двух векторовfunction dot( a : Float3, b : Float3 ) : Float; function dot( a : Float4, b : Float4 ) : Float;
cross(илиcrs) : векторное произведение (cross product) двух векторовfunction cross( a : Float3, b : Float3 ) : Float3;
neg(или унарный-) : арифметическое отрицаниеfunction neg( v : FloatX ) : FloatX;
inv(илиrcp, или1 / x) : обратное значениеfunction inv( v : FloatX ) : FloatX;
sqrt(orsqt) : квадратный кореньfunction sqrt( v : FloatX ) : FloatX;
rsqrt(orrsq, or1 / sqrt(x)) : значение, обратное квадратному корнюfunction rsqrt( v : FloatX ) : FloatX;
log: логарифм (натуральный)function log( v : FloatX ) : FloatX;
exp: экспонентаfunction exp( v : FloatX ) : FloatX;
length(илиlen) : длина значения (?)function length( v : FloatX ) : Float;
normalize(ornorm, ornrm') : нормализация вектораfunction normalize( v : FloatX ) : Float3;
sin: синусfunction sin( v : FloatX ) : FloatX;
cos: косинусfunction cos( v : FloatX ) : FloatX;
abs: модульfunction abs( v : FloatX ) : FloatX;
saturate(илиsat) : выражениеmin(1,max(v,0))function saturate( v : FloatX ) : FloatX;
fract(orfrc) : дробная часть числаfunction fract( v : FloatX ) : FloatX;
int: целая часть числаfunction int( v : FloatX ) : FloatX;
lt(or<) : возвращает (a < b) ? 1 : 0 для каждого значенияfunction lt( v : FloatX ) : FloatX;
lte(or<=) : возвращает (a <= b) ? 1 : 0 для каждого значенияfunction lte( v : FloatX ) : FloatX;
gt(or>) : возвращает (a > b) ? 1 : 0 для каждого значенияfunction gt( v : FloatX ) : FloatX;
gte(or>=) : возвращает (a >= b) ? 1 : 0 для каждого значенияfunction gte( v : FloatX ) : FloatX;
kill: доступно только в пиксельном шейдере. Пропускает запись пикселя если значение меньше нуляfunction kill( v : Float ) : Void;
get: получить цвет из текстуры по указанным текстурным координатам
Доступны следующие флаги:wrap: wrap around the texture bordersclamp: stick to the texture bordersnearest: round-to-nearest pixel readlinear: bilinear pixel readmm_no: отключить mipmappingmm_nearest: round-to-nearest mipmappingmm_linear: линейный mipmappingcentroid: centroid samplefunction get( t : Texture, v : Float4, ....flags ) : Float4;
transpose: транспонирование матрицыfunction transpose( m : MXY ) : MYX;
Swizzling / Masking
Возможно прочитать только часть значения или переупорядочить элементы в значении, используя swizzling. Например :
var tmp : Float4 = [1,2,3,4]; out.x = tmp.x; // скопировать 1 в координату x out.xz = tmp.yz; // скопировать 2 в координату x и 3 в координату z out = tmp.xxww; // скопировать 1 в координаты x и y, а 4 в координаты z и w
Факт записи только части значения называется masking.
Вы можете переупорядочить только доступные компоненты вектора, например нельзя прочитать координату z из переменной типа Float2. Единственное исключение из этого сделано для входных данных и переменных (не временных), из которых можно прочитать все 4 компонента, так как они заполняются нулями, если не были записаны.
Подстановка скалярных значений
Когда операции выполняются над двумя значениями FloatX, если вы используете один Float как одно из этих значений, HxSL автоматически подставит (swizzle) его во все компоненты:
var tmp : Float4 = [1,2,3,4]; out = tmp * 0.5; // умножить все компоненты на 0.5
Это сработает для любого одиночного Float:
var half : Float = 0.5; out = half * tmp; // то же, что и half.xxxx
If / Else
If/Else могут быть внутри выражений :
color = if( red < 0 ) 1 else 0.2;
на самом деле этот код скомпилируется в следующий:
color = (red < 0) * 1 + (red >= 0) * 0.2;
Операторы сравнения можно использовать только внутри if/else
Возможно, полная поддержка If/Else будет добавлена в следующих версиях HxSL.
Циклы For
Циклы можно использовать в HxSL, при условии что они работают с константами:
// посчитать сумму 3x3 семплов текстуры var color = [0,0,0]; for( x in -1...2 ) for( y in -1...2 ) color += tex.get(uv + [x,y]);
Циклы for будут развернуты и шейдер будет такой же, как если написать соответсвующие 9 строк вручную для каждого из значений (x,y).
Транспонирование матриц
Чтобы, к примеру, получить проекцию вектора или умножить матрицы, нужно иметь транспонированную правую часть (из строк получить столбцы). На самом деле матрицы HxSL состоят из четырех Float4, которые (по умолчанию) представляют ее строки, либо столбцы (в режиме транспонирования).
HxSL осуществляет то, что называется выводом транспонирования матрицы (matrix transposition inference). Он определяет по используемой операции в каком виде (транспонированной или нет) должна быть матрица.
К примеру, при проецировании вектора:
function vertext( mpos : Matrix ) { var tpos = pos * mpos; ... }
Матрица mpos станет транспонированной. Любая дальнейшая операция с ее участием в нетранспонированном виде вызовет ошибку.
Построчный доступ
К вектору-строке матрицы можно обратиться с помощью синтаксиса m[3], к примеру этот код вернет четвертую строку матрицы.
Использование HxSL с Flash11
Чтобы использовать шейдеры с Flash11 API, нужно объявить новый шейдер, как в примере ниже:
@:shader({ var input : { pos : Float3, uv : Float2 }; var tuv : Float2; function vertex( mpos : Matrix, mproj : Matrix ) { out = pos.xyzw * mpos * mproj; tuv = uv; } function fragment( t : Texture ) { out = t.get(tuv,wrap); } }) class Shader extends format.hxsl.Shader { }
Этот код сгенерирует методы bind и init для вашего шейдера, которые выполнят все необходимые действия для установки его констант и выберут шейдер в 3D контексте.
Подробности и примеры по использованию шейдеров приведены в Использование Flash 3D API