Введение в пользовательские фильтры (также известные как CSS-шейдеры)

Пользовательские фильтры или CSS-шейдеры, как их раньше называли, позволяют вам использовать возможности шейдеров WebGL с вашим контентом DOM. Поскольку в текущей реализации используемые шейдеры практически такие же, как и в WebGL, вам нужно сделать шаг назад и разобраться в некоторой 3D-терминологии и немного о графическом конвейере.

Я включил записанную версию презентации, которую недавно провел в LondonJS. В видео я делаю обзор 3D-терминологии, которая вам нужна, чтобы понять, с какими типами переменных вы столкнетесь и как вы можете начать играть с пользовательскими фильтрами уже сегодня. Вам также следует взять слайды , чтобы вы могли самостоятельно поиграть с демонстрациями.

Введение в шейдеры

Ранее я написал введение в шейдеры , которое даст вам хорошее представление о том, что такое шейдеры и как их можно использовать с точки зрения WebGL. Если вы никогда не имели дела с шейдерами, вам необходимо прочитать эту информацию, прежде чем идти дальше, поскольку многие концепции и язык пользовательских фильтров основаны на существующей терминологии шейдеров WebGL.

Итак, с учетом вышесказанного, давайте включим пользовательские фильтры и приступим к работе!

Включение пользовательских фильтров

Пользовательские фильтры доступны как в Chrome, так и в Canary, а также в Chrome для Android. Просто зайдите в about:flags и найдите «CSS Shaders», включите их и перезапустите браузер. Теперь вы готовы идти!

Синтаксис

Пользовательские фильтры расширяют набор фильтров, которые вы уже можете применять, например blur или sepia , к вашим элементам DOM. Эрик Бидельман написал для них отличный игровой инструмент , который вам стоит попробовать.

Чтобы применить пользовательский фильтр к элементу DOM, вы используете следующий синтаксис:

.customShader {
    -webkit-filter:

    custom(
        url(vertexshader.vert)
        mix(url(fragment.frag) normal source-atop),

    /* Row, columns - the vertices are made automatically */
    4 5,

    /* We set uniforms; we can't set attributes */
    time 0)
}

Из этого вы увидите, что мы объявляем наши вершинные и фрагментные шейдеры, количество строк и столбцов, на которые мы хотим разбить наш DOM-элемент, а затем любые формы, через которые мы хотим пройти.

И последнее, на что следует обратить внимание: мы используем функцию mix() во фрагментном шейдере с режимом наложения ( normal ) и составным режимом ( source-atop ). Давайте взглянем на сам фрагментный шейдер, чтобы понять, зачем нам вообще нужна функция mix() .

Пиксельное нажатие

Если вы знакомы с шейдерами WebGL, вы заметите, что в пользовательских фильтрах все немного по-другому. Во-первых, мы не создаем текстуры, которые наш фрагментный шейдер использует для заполнения пикселей. Скорее, содержимое DOM, к которому применен фильтр, автоматически сопоставляется с текстурой, а это означает две вещи:

  1. По соображениям безопасности мы не можем запрашивать значения цвета отдельных пикселей текстуры DOM.
  2. Мы не устанавливаем (по крайней мере, в текущих реализациях) конечный цвет пикселя самостоятельно, т.е. gl_FragColor запрещено. Скорее, предполагается, что вы захотите визуализировать содержимое DOM, и вам придется косвенно манипулировать его пикселями через css_ColorMatrix и css_MixColor .

Это означает, что наш Hello World фрагментных шейдеров выглядит примерно так:

void main() {
    css_ColorMatrix = mat4(1.0, 0.0, 0.0, 0.0,
                            0.0, 1.0, 0.0, 0.0,
                            0.0, 0.0, 1.0, 0.0,
                            0.0, 0.0, 0.0, 1.0);

    css_MixColor = vec4(0.0, 0.0, 0.0, 0.0);

    // umm, where did gl_FragColor go?
}

Каждый пиксель содержимого DOM умножается на css_ColorMatrix , который в приведенном выше случае ничего не делает, поскольку является единичной матрицей , и не меняет ни одно из значений RGBA. Если бы мы хотели, скажем, просто сохранить красные значения, мы бы использовали css_ColorMatrix следующим образом:

// keep only red and alpha
css_ColorMatrix = mat4(1.0, 0.0, 0.0, 0.0,
                        0.0, 0.0, 0.0, 0.0,
                        0.0, 0.0, 0.0, 0.0,
                        0.0, 0.0, 0.0, 1.0);

Надеемся, вы увидите, что, умножая значения пикселей 4D (RGBA) на матрицу, вы получаете манипулируемое значение пикселя с другой стороны, и в данном случае такое, которое обнуляет зеленый и синий компоненты.

css_MixColor в основном используется в качестве базового цвета, который вы хотите смешать с содержимым DOM. Смешение осуществляется с помощью режимов наложения, с которыми вы знакомы по художественным пакетам: наложение, экран, осветление цвета, жесткий свет и так далее.

Существует множество способов, которыми эти две переменные могут манипулировать пикселями. Вам следует ознакомиться со спецификацией эффектов фильтра , чтобы лучше понять, как взаимодействуют режимы наложения и композиции.

Создание вершин

В WebGL мы берем на себя полную ответственность за создание трехмерных точек нашей сетки, но в пользовательских фильтрах все, что вам нужно сделать, это указать количество строк и столбцов, которые вы хотите, и браузер автоматически разобьет ваш DOM-контент на группу треугольников. :

Создание вершин
Изображение разбивается на строки и столбцы

Каждая из этих вершин затем передается в наш вершинный шейдер для манипуляций, а это означает, что мы можем начать перемещать их в трехмерном пространстве по мере необходимости. Совсем скоро вы сможете создавать потрясающие эффекты!

Эффект аккордеона
Изображение искажено эффектом аккордеона

Анимация с помощью шейдеров

Добавление анимации в шейдеры делает их интересными и увлекательными. Для этого вы просто используете переход (или анимацию) в своем CSS для обновления унифицированных значений:

.shader {
    /* transition on the filter property */
    -webkit-transition: -webkit-filter 2500ms ease-out;

    -webkit-filter: custom(
    url(vshader.vert)
    mix(url(fshader.frag) normal source-atop),
    1 1,
    time 0);
}

    .shader:hover {
    -webkit-filter: custom(
    url(vshader.vert)
    mix(url(fshader.frag) normal source-atop),
    1 1,
    time 1);
}

Итак, в приведенном выше коде следует отметить, что во время перехода время будет уменьшаться с 0 до 1 . Внутри шейдера мы можем объявить единое time и использовать любое его текущее значение:

    uniform float time;

uniform mat4 u_projectionMatrix;
attribute vec4 a_position;

void main() {
    // copy a_position to position - attributes are read only!
    vec4 position = a_position;

    // use our time uniform from the CSS declaration
    position.x += time;

    gl_Position = u_projectionMatrix * position;
}

Начинайте играть!

С пользовательскими фильтрами очень интересно играть, а без них создать потрясающие эффекты сложно (а в некоторых случаях невозможно). Это еще только начало, и все немного меняется, но их добавление добавит немного шоу-бизнеса в ваши проекты, так почему бы не попробовать?

Дополнительные ресурсы