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 float
  • Matrix or M44 : матрица float 4x4
  • M33, M43 and M34 : матрица соответствующей формы
  • 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^b
    function 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 (or sqt) : квадратный корень
    function sqrt( v : FloatX ) : FloatX;
  • rsqrt (or rsq, or 1 / 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 (or norm, or nrm') : нормализация вектора
    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 (or frc) : дробная часть числа
    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 borders
    • clamp : stick to the texture borders
    • nearest : round-to-nearest pixel read
    • linear : bilinear pixel read
    • mm_no : отключить mipmapping
    • mm_nearest : round-to-nearest mipmapping
    • mm_linear : линейный mipmapping
    • centroid : centroid sample
      function 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

version #10508, modified 2011-04-27 16:16:11 by Scythian